Skip to main content

06 · Deep Research:让一群 Agent 帮你写一份带引用的报告

零、开始之前

前置前五篇都要读过 —— 本篇没有新范式,它是前五个的组合。

产出:一个六节点研究系统的设计,以及每个环节在防什么。

本篇有一份别处没有的材料:Anthropic 把线上 Research 功能的提示词原文开源了(MIT,在 claude-cookbooks 里)。那份 23KB 的编排提示词把「派几个人、每人几次工具调用、什么时候收手」全写成了明文,读它比读任何代码都值。

一、先看一个真实问题

你要写一份报告:

「2026 年主流 Agent 框架的选型对比,要有数据支撑,要能引用来源。」

01 ReAct:搜十几次,上下文爆了(05 第一节讲过)。

05 Supervisor:派三个子 Agent 分别查三个框架,上下文问题解决了,并行也有了。

但你会发现报告还是不能用,因为缺两样东西:

缺什么具体表现
没有引用报告里说「LangGraph 有 4 万星」,你没法验证这是查到的还是编的
信息损耗子 Agent 返回的结论太粗,写报告时发现关键细节没了,只能再派一轮

Deep Research 就是在 Supervisor 的基础上,补上这两块。

二、概念:它是前五个范式的叠加

先看整体形状:

Anthropic 公开的官方架构图,与上图一一对应:

Anthropic 多智能体研究系统的编排器-工人架构

图 6-1 主管(LeadResearcher)协调多个子代理并行探索不同研究方向

图片来源:Anthropic — How we built our multi-agent research system

对照前五篇看,只有黄色那两块是新的:

组件来自哪一篇
写研究简报03 Plan 的规划阶段
主管派活05 Supervisor
子研究员内部循环01 ReAct
并行02 Workflow 的并行模式
「还有缺口就再派一轮」04 Reflection
③ 压缩Deep Research 特有
⑤ 引用Deep Research 特有

所以这一篇的重点,就是搞懂这两个新环节,以及主管的派活策略。

2.1 它和 03 Plan-and-Execute 到底差在哪

这两个最容易混,因为都是「先出计划再执行」。区别在于计划是给谁看的:

03 Plan-and-Execute06 Deep Research
计划是什么给自己看的待办清单给别人看的派活书
谁执行同一个 Agent,自己逐条做一批子代理,每人领一条
执行方式串行并行
上下文全在一份历史里每个子代理一份,互相看不见
计划的粒度「改 A 文件」「跑测试」——动作「研究 A 库的性能,用这些信源,输出事实清单」——带边界和验收标准的任务书
解决的问题别忘了目标(防漂移)一个上下文装不下(防爆)
结果怎么回来就在当前历史里压缩后回传,原始材料丢弃

一句话:03 的计划是提醒自己,06 的计划是指挥别人

所以判断该用哪个很简单 —— 问一句:这些子任务能不能同时干?

  • 不能(改文件有依赖、必须按顺序)→ 03,加了并行也没用,只多花协调成本
  • 能(查五个库互不相干)→ 06

还有一个信号:如果做完一步产生的中间材料,后面几步根本用不上(比如查资料的原始网页),那就该用 06 —— 让它在子代理的上下文里烧掉,别污染主线。

三、拆 Anthropic 的生产提示词

三个文件,在 claude-cookbooks/patterns/agents/prompts/

文件大小角色
research_lead_agent.md23KB主管
research_subagent.md9.1KB子研究员
citations_agent.md2.9KB引用代理

3.1 主管做的第一件事:判题型

主管拿到任务后,不是马上派活,而是先判断这是哪一类问题。因为不同题型的派活方式完全不同。

题型什么样的问题怎么派
Depth-first
深度优先
一个问题,需要多个角度来看派多个子代理,各自用不同的方法论/视角攻同一个问题
Breadth-first
广度优先
能拆成互相独立的子问题每个子问题一个子代理,天然并行
Straightforward
直球
单点事实查询1 个子代理就够

原文给的例子很有辨识度:

  • 「抑郁症最有效的治疗方法是什么」→ 深度优先(多种疗法、多个学派的视角)
  • 「比较三个北欧国家的经济体制」→ 广度优先(三个国家可以独立研究)
  • 「东京现在人口多少」→ 直球

这个分类是整个编排的分叉点。 多数自建 Deep Research 系统的问题就出在这儿:不管什么问题都用同一套派活策略

3.2 派几个人:有一张明确的数字表

这是最实用的一段。Anthropic 直接给了数量指南:

复杂度子代理数例子
简单1「今年报税截止日是哪天」
标准2-3「比较三大云厂商」→ 一家一个
中等3-5「分析 AI 对医疗的影响」→ 监管/临床/经济/技术四个面
5-10(硬上限 20「财富 500 强 CEO 的出生地和年龄」→ 10 个代理各管 50 个

三条附加规则,每条都在防一种具体的浪费

① 「即使是最简单的查询,也总是至少创建 1 个子代理」

这条反直觉:查个人口数字,主管自己搜一下不就完了?

但原文的理由是「以确保正确收集来源」—— 主管自己搜的话,就没有独立的信息采集轨迹,下游的引用环节会挂不上。这是为了 ⑤ 而付的固定成本。

② 「绝不要超过 20 个子代理,除非绝对必要」

原文的措辞值得原样记住:

如果一个任务看起来需要超过 20 个子代理,通常意味着你应该重构方法……优先选择更少、更强的子代理,而不是许多过于狭窄的。更多子代理 = 更多开销。

③ 「避免子代理之间重叠」

重叠是并行编排里最贵的浪费:三个代理搜了同一批网页,你付了三份钱,拿到一份信息

3.3 派活时必须说清楚的七件事

主管给子代理的任务描述,必须包含:

  1. 具体研究目标 —— 理想情况下每个子代理只有 1 个核心目标
  2. 期望的输出格式 —— 实体列表?事实报告?还是回答某个具体问题
  3. 背景上下文 —— 用户的问题是什么,这个子代理在整体计划里的位置
  4. 要回答的关键问题
  5. 建议的起点和信源 —— 什么算高质量来源,哪些来源不可信要避开
  6. 该用哪些工具 —— 网页搜索?还是内部的 Drive / Gmail / Slack
  7. 精确的范围边界 —— 防止研究漂移

第 7 条正好对应 05 的排查表里那条「派出去干 A,回来交了 B」。

Anthropic 还在提示词里放了一个完整的正面范例,是关于半导体供应链的,足足一百多个词,具体到「去 SEC EDGAR 数据库查」「优先原始信源而非新闻聚合」「重点关注当前瓶颈和新厂产能预测」。

这个范例本身就说明了标准有多高。 你随手写的「研究一下 X」离这个差得非常远 —— 这大概是自建系统效果差的头号原因。

3.4 子研究员:预算是硬性的

子代理那份 9.1KB 的提示词,核心是给它一个明确的工具调用预算

任务难度允许的工具调用次数
简单(「今年报税截止日」)< 5
中等5
困难~10
非常困难 / 多部分最多 15

然后是硬上限:

为防止系统过载,要求你保持在 20 次工具调用约 100 个来源以内。这是绝对最大上限。如果超过这个限制,子代理将被终止。

注意这是两层保险

为什么要两层?因为只靠提示词约束,模型一定会超。它总觉得再查一条就更全了。

子代理内部跑的是显式的 OODA 循环(observe 观察 / orient 定位 / decide 决策 / act 行动),要求最少 5 次、最多 10 次工具调用。本质上就是 01 ReAct,只是把「想什么」结构化了。

3.5 主管的三条硬规则

① 收益递减就立刻停

当进一步研究已经收益递减、你已经能给出足够好的答案时,停止进一步研究,不要创建任何新子代理。

Deep Research 最大的成本黑洞就是「再查一轮说不定更全」。这条规则把「什么时候够了」显式交给主管判断。

② 绝不让子代理写最终报告

绝不创建子代理来生成最终报告 —— 你自己写,永远不允许用子代理来创建报告。

为什么?因为只有主管看过全部子代理的结果。 派个子代理去写报告,它拿到的是二手摘要,信息要损耗两次

③ 主管不做主要研究

你的主要角色是协调、指导和综合 —— 不是自己做主要研究

只有当某个关键问题子代理没覆盖到时,主管才自己动手。

②和③合起来是一条完整的分工原则

主管不查资料,但必须自己写结论。

3.6 引用为什么要单独一个 Agent

citations_agent.md 只有 2.9KB,职责单一:给已经写好的报告逐句挂来源

为什么不让主管边写边挂?因为「写得流畅」和「挂得准确」是两个互相拉扯的目标。混在一起,模型会为了行文顺畅而牺牲引用精度 —— 该标的地方不标,或者标一个大概相关的。

这和 04 Reflection 里「生成和批判要分开」是完全相同的道理:一次只让模型追求一个目标。

四、拆代码骨架:open_deep_research

官方还公开了完整生命周期图,可与下面的六节点代码骨架对读:

多智能体研究系统的完整工作流

图 6-2 从用户提问、主管迭代、子代理并行检索、综合,到 CitationAgent 挂引用的全流程

图片来源:Anthropic — How we built our multi-agent research system

提示词讲完,看代码怎么组织。拆 langchain-ai/open_deep_research(12,636★,MIT)的 deep_researcher.py,30KB,六个节点

async def clarify_with_user(...)        # ① 需要跟用户确认吗
async def write_research_brief(...) # ② 把对话压成研究简报
async def supervisor(...) # ③ 主管决策
async def supervisor_tools(...) # 主管派活的执行
async def researcher(...) # ④ 子研究员 ReAct 循环
async def researcher_tools(...)
async def compress_research(...) # ⑤ 压缩
async def final_report_generation(...) # ⑥ 写报告

4.1 三条独立的消息通道

这是状态设计里最值得学的一点:

class AgentState(MessagesState):
supervisor_messages: ... # 主管的对话
research_brief: Optional[str]
raw_notes: list[str] = [] # 原始材料
notes: list[str] = [] # 压缩后的

class ResearcherState(TypedDict):
researcher_messages: ... # 子研究员自己的对话
compressed_research: str

用户对话、主管对话、子研究员对话是三份完全独立的消息历史。

这是 05 里「上下文隔离」的彻底版本 —— 不是靠 output_mode 事后裁剪,而是从状态结构上就分开

raw_notesnotes 也是一对:前者存原始材料,后者存压缩结果。写报告时用 notes,需要溯源时查 raw_notes

4.2 write_research_brief:被低估的一步

用户的原话是口语的、模糊的、带隐含前提的。直接把对话丢给主管派活,子代理拿到的任务描述会继承这份模糊。

这个节点的作用,是把对话固化成一份书面任务书,后面所有子代理都以它为准。

对应 3.3 说的「任务描述七要素」—— 简报本身写不清楚,七要素就无从谈起。

4.3 compress_research:实现方式很巧妙

# 在子研究员自己的消息历史后面,追加一条「现在切换到压缩模式」的指令
researcher_messages.append(HumanMessage(content=compress_research_simple_human_message))

compression_prompt = compress_research_system_prompt.format(date=get_today_str())
messages = [SystemMessage(content=compression_prompt)] + researcher_messages
response = await synthesizer_model.ainvoke(messages)

注意它不是新开一个 Agent 来读摘要,而是让子研究员自己切换到压缩模式。

差别在哪?

做法压缩者看到的
新开一个 Agent只有子研究员交出来的结果
切换模式(这个实现)全部研究过程,包括试错、被否定的线索

看得越全,压缩质量越高。

还有一个实际的成本优化:压缩用的是单独配置的模型configurable.compression_model)。压缩是个便宜活,没必要用最贵的模型。

外面还包了 3 次重试,专门处理压缩时撞上 token 上限的情况。

4.4 并发上限:超出的不丢弃,而是回一条错误

这段是全文件最值得学的:

allowed  = conduct_research_calls[:configurable.max_concurrent_research_units]
overflow = conduct_research_calls[configurable.max_concurrent_research_units:]

tool_results = await asyncio.gather(*research_tasks)

# 超出的部分,每个都回一条错误消息
for overflow_call in overflow:
ToolMessage(
content=f"Error: Did not run this research as you have already exceeded the maximum "
f"number of concurrent research units. Please try again with "
f"{configurable.max_concurrent_research_units} or fewer research units.",
tool_call_id=overflow_call["id"],
)

主管一口气派了 12 个,系统上限 5 个 —— 前 5 个正常跑,后 7 个各回一条「你派太多了,请用 5 个或更少再试」

这个模式在本专题里已经是第三次出现了

出处场景做法
03并行调了两次清单工具回一条错误消息,让模型改成单次
05并行交接产生非法历史清理掉不属于该分支的调用
这里超过并发上限回一条可操作的错误

在 Agent 编排里,错误处理的第一原则是「让模型能读懂并自己改正」,而不是抛异常。

判断标准:你的错误信息,模型读完知道下一步该怎么做吗?

  • ❌ 「Invalid argument」
  • ✅ 「你派了 12 个但上限是 5 个,请用 5 个或更少再试」

顺带,tool_call_id 必须原样带回 —— 回想 01,少一个 id 整轮消息就非法了。

4.5 两层迭代熔断

# 主管这一层
exceeded_allowed_iterations = research_iterations > configurable.max_researcher_iterations
# 子研究员那一层
exceeded_iterations = state.get("tool_call_iterations", 0) >= configurable.max_react_tool_calls

主管派活的轮数、子研究员的工具调用次数,各有各的上限

对应 3.4 说的双层预算设计 —— 一层管不住

五、什么时候停,以及子代理挂了怎么办

前面讲的都是「顺利跑完」的路径。但这个范式最贵、跑得最久,真正的工程量在这一节:什么时候该收手,以及有子代理失败时是忽略、重试,还是整个放弃。

5.1 停止条件有三层,缺一层都会失控

机制谁来判断
判断层收益递减就停,不再派新子代理(3.5 ①)模型(主管自己判断)
预算层子代理:简单任务 < 5 次工具调用,最多 15 次(3.4)模型(写在提示词里让它自觉)
熔断层max_researcher_iterations(主管派活轮数)、max_react_tool_calls(子研究员)、20 次工具调用 / 100 来源硬上限系统(超了直接终止)

三层是递进的,不是三选一:模型的自觉判断最灵活但不可靠,预算给它一个量化参照,熔断兜住最坏情况。

只靠判断层,你会遇到「再查一轮说不定更全」的无限循环;只靠熔断层,简单问题也会跑满预算才停。

5.2 失败处理:一条五级降级阶梯

open_deep_research 的做法值得整个抄下来 —— 异常在每一级都被转成"内容"继续往上走,而不是往上抛

L1 是整条阶梯的地基,只有六行:

async def execute_tool_safely(tool, args, config):
try:
return await tool.ainvoke(args, config)
except Exception as e:
return f"Error executing tool: {str(e)}"

搜索接口挂了、网页抓取超时、返回了非法 JSON —— 统统变成一句话交给子研究员。它看到「这个工具报错了」,下一轮自己换关键词或换工具。

这正是 Anthropic 在工程博客里说的那条经验:

让 agent 知道某个工具正在失败、并让它自己适应,效果出奇地好。

5.3 直接回答「无限重试都没用怎么办」

第一,没有无限重试。 上面每一级都有硬上限,最多 3 次。这是刻意的 —— 重试的边际收益衰减极快,而 Deep Research 每次重试都是真金白银。

第二,重试必须改变条件,否则只是重复失败。 这是整节最关键的一条。ODR 里两处重试都在重试前先改变了输入

场景重试前做了什么
压缩时 token 超限remove_up_to_last_ai_message() 砍掉一段历史
生成报告时 token 超限把材料截断 10%,每轮再降 10%

不改条件的重试(同样的 prompt 再发一次)只对瞬时故障有效 —— 网络抖动、限流。对确定性失败(上下文超限、权限不足、参数非法)重试多少次都是一样的结果。

所以判断该不该重试的标准是:这次失败的原因,下次还在不在?

第三,最终降级不是崩溃,是把失败写成文本交上去。 这是和普通程序最不一样的地方:

子代理 3 彻底失败
↓ 不是抛异常终止整个任务
主管收到一条 "Error synthesizing research report: Maximum retries exceeded"
↓ 当作一份(内容为"失败"的)成果
主管拿着 4 份成功 + 1 份失败继续综合

报告里如实写明这部分没查到,或主管决定重派一个子代理补上

一份缺了 1/5 的报告,远好过什么都没有。 这就是为什么 L4 那一层要把错误当内容收下 —— 它把「局部失败」隔离在局部。

源码里一处值得注意的写法:主管那层的兜底是 if is_token_limit_exceeded(e, ...) or True:。那个 or True任何异常都走同一条路 —— 结束研究阶段,用手上已有的材料去写报告。写法很脏(条件判断形同虚设),但意图是明确的:宁可交一份不完整的报告,也不要让整个任务归零。

5.4 长任务还需要检查点

Deep Research 动辄跑几分钟到几十分钟,进程被杀、机器重启都是常态。Anthropic 的做法是:

我们构建了能从错误发生时的位置恢复的系统,而不是从头重启。 我们把 Claude 的适应能力与确定性的保障措施(重试逻辑、定期检查点)结合起来。

还有一条很具体的经验:主管会把研究计划写进外部记忆,因为「一旦上下文超过 20 万 token 就会被截断,而保住计划很重要」。

这条和 03 Plan-and-Execute 4.4 节讲的是同一件事 —— 计划必须存在上下文之外,否则它迟早会被自己产生的材料挤掉。

六、关于 OpenAI / Gemini 的 Deep Research

必须说清楚:它们没有源码,只有 API 文档、产品博客和 system card。

所以本篇的做法是:

  • 编排原理用 Anthropic 的公开提示词讲 —— 那是真在线上跑的
  • 代码实现用 open_deep_research 讲 —— MIT 协议,可以跑可以改
  • OpenAI 那个只讲从 API 行为能观察到的部分,不假装拆过它

其他值得参考的开源实现:

项目Star特点
dzhng/deep-research19,571最简实现,想快速理解整个流程先看这个
gpt-researcher29,035出现最早,工程完整度高
smolagents/examples/open_deep_research28,873(主仓)HuggingFace 复刻,跑 GAIA 基准
Hello-Agents 第十四章73,668(主仓)中文,TODO 驱动的三阶段流程,2152 行带 12 张图

七、常见故障与排查

你看到的现象原因怎么修
三个子代理搜了同一批网页派活时没划分边界任务描述七要素的第 7 条(3.3)
一轮接一轮,成本失控没有收敛判断「收益递减就停」+ 双层迭代熔断(3.5 ①、4.5)
主管上下文照样爆子代理结果全量回传加压缩环节(4.3)+ 独立消息通道(4.1)
报告有引用,但对不上原文让写手兼职挂引用独立的引用代理(3.6)
报告内容空泛,像摘要的摘要让子代理写了最终报告主管必须自己写(3.5 ②)
主管一次派 20 个,系统崩了没有并发上限上限 + 溢出回错误(4.4)
所有子代理都跑偏研究简报本身就模糊先固化任务书(4.2)
简单问题也跑了五分钟没有判题型,一律按复杂处理加三分类(3.1)
一个子代理挂掉,整个任务归零异常向上抛了每级把异常转成内容继续上行(5.2)
反复重试同一个失败,次次一样重试前没改变任何条件先砍历史/截断再试;判断失败原因下次还在不在(5.3)
搜索接口偶发报错就整轮白跑工具异常没有兜底execute_tool_safely 把异常转成字符串(5.2 L1)
报告里看不出哪部分没查到失败被静默吞掉让失败以文本形式进入主管视野(5.3)
跑到一半进程被杀,只能从头再来没有检查点检查点 + 断点恢复;计划存到上下文之外(5.4)

八、全局定位

Deep Research 在最右端:最贵、最慢、最不可预测,但能干最难的活

什么时候不该用

  • 问题有确定答案 —— 「Python 怎么读 CSV」,一次搜索的事,它会给你一篇三千字报告
  • 信息源封闭且少 —— 只需要查内部两个文档,直接用 RAG
  • 要求快 —— 一次完整的 Deep Research 是分钟级的,不是秒级
  • 成本敏感 —— 一次运行可能是几十次模型调用加上百次网页抓取,单次成本比普通问答高两三个数量级

用它之前先确认:这个问题值不值得花这个钱。

九、小结与检查清单

核心三句话

  1. Deep Research 不是新范式,是前五个的组合,新增的只有压缩引用
  2. 派活的质量决定一切 —— 任务描述七要素,一条都不能少
  3. 停止与失败处理各有一套分层结构:停止三层(模型判断 / 提示词预算 / 系统熔断),失败五级降级(异常转成内容继续上行,而不是上抛)

自查清单

  • 有没有先把用户对话固化成一份书面研究简报?
  • 主管有没有先判题型(深度/广度/直球)再派活?
  • 派几个子代理,有没有依据?还是拍脑袋?
  • 任务描述里,七要素(尤其是范围边界和输出格式)齐全吗?
  • 子代理有工具调用预算吗?系统层有强制上限吗?
  • 子代理回来之前有压缩环节吗?压缩用的是便宜模型吗?
  • 最终报告是主管自己写的,还是派给子代理写的?
  • 引用是单独一个环节,还是让写手顺手挂的?
  • 并发有上限吗?超出的调用是丢弃还是回一条可操作的错误?
  • 有「收益递减就停」的判断吗?
  • 停止条件三层都有吗(模型判断 / 提示词预算 / 系统熔断)?
  • 工具异常是抛出去,还是转成字符串交给子代理自己适应?
  • 每处重试在重试前改变了条件吗?还是原样再发一次?
  • 单个子代理彻底失败时,其余子代理的成果保得住吗?
  • 失败会以文本形式出现在主管视野里,还是被静默吞掉?
  • 长任务有检查点吗?研究计划有没有存在上下文之外?

参考资料

资料位置协议
Anthropic 生产提示词原文patterns/agents/prompts/research_lead_agent.md 23KB、research_subagent.md 9.1KB、citations_agent.md 2.9KBMIT
代码骨架open_deep_research/deep_researcher.py 30KB + prompts.py 21KBMIT
工程复盘Anthropic — How we built our multi-agent research system——
中文实战Hello-Agents 第十四章CC BY-NC-SA 4.0

下一篇:07 · CodeAct —— 换掉「动作」的表达方式。